package game;

import java.awt.Point;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

import javax.swing.JOptionPane;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

import xml.XMLcommon;
import xml.XMLizeable;

/**
 * <p>Runs the chess game according the the rules of chess on wikipedia.These rules include:
 * the 50 move rule, castling, en passant, promotion, check and checkmate, resigning and 
 * draw.</p>
 * 
 * @author Nicole Waldrum
 * @date January 30, 2010
 * @version 1.0
 *
 * @author Mark Kelly
 * @date February 20, 2010
 * @version 2.0
 * 
 * @author Trevor Bastian
 * @date March 9, 2010
 * @version 3.0
 * @version 4.0
 * 
 * @author Mark Kelly
 * @date March 28, 2010
 * @version 4.0
 */

public class ClassicChess extends GameType implements XMLizeable {
	private static final String FIFTY_MOVES_MESSAGE = "Tie. It has been 50 consecutive turns since a pawn was moved.";
	private Team turn;
	private int turnsSincePawnMove;

	/**
	 * <p>Creates a new instance of <tt>ClassicChess</tt></p>
	 */
	public ClassicChess() {
		super();
		turn = Team.WHITE;
		turnsSincePawnMove = 0;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * <p>In the case of <tt>ClassicChess</tt>, do nothing.</p>
	 */
	@SuppressWarnings("unchecked")
	public void pieceMoved (GameState gs, Piece p, ChessBoard b) {
		try {
			if(hasWon(turn)) {
				state.stateChanged(state);
				state.notifyGameWon(turn);
			}
		}
		catch (Exception e){}
		
		turnsSincePawnMove++;
		if (turnsSincePawnMove == 50)
			state.notifyGameTied(FIFTY_MOVES_MESSAGE);
		
		if (p instanceof Pawn) {
			// reset the '50 move counter'
			turnsSincePawnMove = 0;
			// promotion
			if (p.getLocation().y == 8 || p.getLocation().y == 1) {
				try {
					Class<? extends Piece> typeChoice = null;
					try {
						typeChoice = (Class<? extends Piece>)Class.forName("game." + JOptionPane.showInputDialog(null, typeChoice, "Message", JOptionPane.QUESTION_MESSAGE));
					} catch (ClassNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					p = p.changeType(typeChoice);
					b.movePiece(p, p.getLocation());
					state.stateChanged(state);
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (SecurityException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InstantiationException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
		turn = turn.other();
	}
	
	/**
	 * {@inheritDoc}
	 * 
	 * <p>In the case of <tt>ClassicChess</tt>, 32 <tt>Pieces</tt> are placed on the board: 16 black and 16 white.</p>
	 */
	@Override
	public void setupBoard() {
		HashSet<Piece> pieces = new HashSet<Piece>();
		
		// White Pieces
		// Pawns
		for (int i = 1; i <=8; i++) {
			pieces.add(new Pawn(new Point(i, 2), Team.WHITE));
		}
		// Rooks
		pieces.add(new Rook(new Point(1,1), Team.WHITE));
		pieces.add(new Rook(new Point(8,1), Team.WHITE));
		// Knights
		pieces.add(new Knight(new Point(2,1), Team.WHITE));
		pieces.add(new Knight(new Point(7,1), Team.WHITE));
		// Bishops
		pieces.add(new Bishop(new Point(3,1), Team.WHITE));
		pieces.add(new Bishop(new Point(6,1), Team.WHITE));
		// Queen
		pieces.add(new Queen(new Point(4,1), Team.WHITE));
		// King
		pieces.add(new King(new Point(5,1), Team.WHITE));
		
		// Black Pieces
		// Pawns
		for (int i = 1; i <=8; i++) {
			pieces.add(new Pawn(new Point(i, 7), Team.BLACK));
		}
		// Rooks
		pieces.add(new Rook(new Point(1,8), Team.BLACK));
		pieces.add(new Rook(new Point(8,8), Team.BLACK));
		// Knights
		pieces.add(new Knight(new Point(2,8), Team.BLACK));
		pieces.add(new Knight(new Point(7,8), Team.BLACK));
		// Bishops
		pieces.add(new Bishop(new Point(3,8), Team.BLACK));
		pieces.add(new Bishop(new Point(6,8), Team.BLACK));
		// Queen
		pieces.add(new Queen(new Point(4,8), Team.BLACK));
		// King
		pieces.add(new King(new Point(5,8), Team.BLACK));
		
		// add all of the Pieces to the board
		state.setBoard(new ChessBoard(pieces));
		
	}

	public boolean takeTurn(Point start, Point dest) {
		ChessBoard board = state.getBoard();
		
		if (start == null)
			return false;
		
		if(board.hasPieceAt(start)){
			if (board.getPieceAt(start).getTeam() != turn)
				return false;
			
			return movePiece(start, dest);
		}
		return false;
	}
	
	/**
	 * {@inheritDoc}
	 * 
	 * <p>In the case of <tt>ClassicChess</tt>, a <tt>Piece</tt> can only land on a <tt>Piece</tt> of the opposite colour.</p>
	 */
	public boolean isValidMove(Piece piece, Point dest) {
		ChessBoard board = state.getBoard();
		
		if (piece instanceof King) {
			// Castling
			if (!piece.hasMoved()) {
				if (dest.x == 3) {
					Point point = new Point (1, piece.getLocation().y);
					// check if the rook is there and hasn't moved
					if (board.hasPieceAt(point)) {
						Piece p = board.getPieceAt(point);
						if (!p.hasMoved()) {
							point = new Point (4, piece.getLocation().y);
							// check that all the required squares are free
							for (Point pt: p.checkMovePattern(point))
								if (board.hasPieceAt(pt) || board.hasPieceAt(point))
									return false;
							// Check if the king is in check
							if (!(isKingInCheck(piece.getTeam(), dest).size() == 0))
								return false;
							// move the pieces!
							board.movePiece(p, point);
							return true;
						}
					}
				} else if (dest.x == 7) {
					Point point = new Point (8, piece.getLocation().y);
					if (board.hasPieceAt(point)) {
						// check if the rook is there and hasn't moved
						Piece p = board.getPieceAt(point);
						if (!p.hasMoved()) {
							point = new Point (6, piece.getLocation().y);
							// check that all the required squares are free
							for (Point pt: p.checkMovePattern(point))
								if (board.hasPieceAt(pt) || board.hasPieceAt(point))
									return false;
							
							// Check if the king is in check
							if (!(isKingInCheck(piece.getTeam(), dest).size() == 0))
								return false;
							// move the pieces!
							board.movePiece(p, point);
							return true;
						}
					}
				}
			}
			
			if (isKingInCheck(piece.getTeam(), dest).size() != 0)
				return false;
			
		} else if (piece instanceof Pawn) {
			// Special case of pawn moving (Taking a piece)
			if (board.hasPieceAt(dest) && board.getPieceAt(dest).getTeam() == piece.getTeam().other()) {
				int dx = Math . abs ( piece . getLocation () . x - dest . x );
				int dy = dest . y - piece . getLocation () . y;
				
				// if its trying to move diagonally
				if (dx == 1) {
					// and the right direction
					if (piece.getTeam() == Team.WHITE && dy == 1) {
						return true;
					} else if (piece.getTeam() == Team.BLACK && dy == -1) {
						return true;
					}
				}
				return false;
			}
			// else if its an invalid move according to normal rules, return false
			if (!piece.isValidMove(piece, dest))
				return false;
		}

		// check the move pattern (required free squares are actually free)
		for (Point point : piece.checkMovePattern(dest))
			if (board.hasPieceAt(point))
				return false;

		return true;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * <p>In the case of <tt>ClassicChess</tt>, the opposing <tt>King</tt> must be in check and unable to escape.</p>
	 */
	public boolean hasWon (Team team) {
		King k = (King)state.getBoard().getPieceList(King.class, team.other()).get(0);
		Point king = k.getLocation();
		List<Piece> threats;
		
		// Populate a list of pieces that can attack the king
		threats = isKingInCheck (k.getTeam(), king);
		
		if(threats.size() == 0){
			return false;
		}
		
		if(threats.size() > 1){
			return true;
		}

		// Check if the king can move out of the way
		/*for (int i = king.x - 1; i <= king.x + 1; i++){
			for (int j = king.y - 1; j <= king.y +1; j++){
				if ((isKingInCheck(k.getTeam(), new Point(i, j)).size() == 0) && state.isValidMove (k, new Point (i, j))){
					System.out.println("Ninja");
					return false;		// The king can ninja his way out of it this time!
				}
				System.out.println("No  ninja");
			}
		}*/
		//check if you can take the threats
		for(Piece t: threats){
			for(Piece p: state.getBoard().getPieceList(Piece.class, team.other())){
				//if you can take the threat then attempt it
				if(p.isValidMove(p, t.getLocation()) && p.getTeam() != t.getTeam() && checkClearPath(p.checkMovePattern(t.getLocation()), state.getBoard())){
					if(!isKingStillInCheck(t.getLocation(), k.getTeam(), p, king)){ 
						System.out.println("Take");
						return false;
					}
				}
				System.out.println("No Take");
			}
		}

		//check to block
		for(Piece t: threats){
			for(Piece p: state.getBoard().getAllPieces()) {
				//if you can block the threat then attempt it
				for(Point point : t.checkMovePattern(king)){
					if(isValidMove(p, point) && p.getTeam() != t.getTeam()){
						if(!isKingStillInCheck(t.getLocation(), team, p, king)){
							System.out.println("Block");
							return false;
						}
					}
					System.out.println("No block");
				}
			}
		}	

		// somebody won
		return true;
	}

	/**
	 * <p>Checks if the king is still in check</p>
	 * 
	 * @param target location attacker wants to move to
	 * @param currentTeam team whose turn it is
	 * @param attacker the piece attacking
	 * @param king the king who we want to check is in check
	 * @return true if he is, false if he isn't
	 */
	private boolean isKingStillInCheck(Point target, Team currentTeam, Piece attacker, Point king){
		Point temp = attacker.getLocation();
		List<Piece> newThreats;
		attacker.setLocation(target);

		newThreats = isKingInCheck(currentTeam, king);
		
		if(newThreats.size() == 0){
			attacker.setLocation(temp);
			return true;
		}
		
		attacker.setLocation(temp);
		return false;
	}

	/**
	 * <p>Creates a <tt>List</tt> of every <tt>Piece</tt> that would threaten a <tt>King</tt>, on the specified <tt>Team</tt> at the specified <tt>Point</tt></p>
	 * 
	 * @param team the king in question's team
	 * @param point the kings location
	 * @return a list of pieces that are threatening the king. Empty list if not in check
	 */
	public List<Piece> isKingInCheck (Team team, Point point) {
		Team other = team.other();
		ArrayList<Piece> threats = new ArrayList<Piece>();

		// Look through every piece
		for (Piece p : state.getBoard().getAllPieces()){
			// if its a member of the opposing team AND could possibly kill the king, add it to the list
			if (p.getTeam().equals(other) && p.isValidMove(p, point) && checkClearPath(p.checkMovePattern(point), state.getBoard())){
				threats.add(p);
			}
		}
		return threats;
	}
	
	/**
	 * <p>Checks if the given path is clear</p>
	 * 
	 * @param path the path to check
	 * @param board the board to check it against
	 * @return true if its clear, false if its not
	 */
	private boolean checkClearPath (List<Point> path, ChessBoard board) {
		for (Point p: path)
			if (board.hasPieceAt(p))
				return false;
		return true;
		
	}
	
	/**
	 * <p>Moves the <tt>Piece</tt> at 'start' to 'dest' if its a valid move.</p>
	 * 
	 * @return true if valid move, false otherwise
	 */
	public boolean movePiece(Point start, Point dest) {
		ChessBoard board = state.getBoard();
		
		if (board.hasPieceAt(start)) {
			Piece p = board.getPieceAt(start);
			// check if its a valid move and if the pawn exception is in effect OR if the player is castling
			if (state.isValidMove(p, dest)
					|| (p instanceof Pawn && this.isValidMove(p, dest))
					|| (p instanceof King && this.isValidMove(p, dest))) {
				// check if you're taking a piece or not
				if (board.hasPieceAt(dest)){
					if (board.getPieceAt(dest).getTeam() != p.getTeam()) {
						board.removePiece(board.getPieceAt(dest));
					} else {
						return false;
					}
				}
				board.movePiece(p, dest);
				state.notifyPieceMoved(p, board);
				return true;
			}
		}
		// invalid move
		return false;
	}

	private ClassicChess (Element elem) {
		this.turn = Team.WHITE;
		this.turn = Team.fromString (XMLcommon.getTextValue(elem, "Turn"));
		this.turnsSincePawnMove = Integer.parseInt(XMLcommon.getTextValue(elem, "noPawnMove"));
	}
	
	public static ClassicChess importFromXML(Element elem) {
		return new ClassicChess (elem);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public Element toXML (Document doc) {
		Text txt;
		Element element;
		Element gameType = doc.createElement("GameType");
		gameType.setAttribute("GameType", ClassicChess.class.getName());
		
		element = doc.createElement("Turn");
		txt = doc.createTextNode(turn.toString());
		element.appendChild(txt);
		gameType.appendChild(element);
		
		element = doc.createElement("noPawnMove");
		txt = doc.createTextNode("" + turnsSincePawnMove);
		element.appendChild(txt);
		gameType.appendChild(element);
		
		return gameType;
	}

	@Override
	public void needPieceChoice(GameState gs) {
		// TODO Auto-generated method stub
		
	}
}
